为什么深度神经网络这么难训练?| 赠书
导读:本文内容节选自《深入浅出神经网络与深度学习》一书,由Michael Nielsen所著,他是实验媒体研究工作室的联合创始人,曾是 YC Research 的 Research Fellow。。
本书深入了讲解神经网络和深度学习技术,侧重于阐释深度学习的核心概念。作者以技术原理为导向,辅以贯穿全书的 MNIST 手写数字识别项目示例,介绍神经网络架构、反向传播算法、过拟合解决方案、卷积神经网络等内容,以及如何利用这些知识改进深度学习项目。学完本书后,读者将能够通过编写 Python 代码来解决复杂的模式识别问题。
了解关于深度学习的更多干货知识,关注AI科技大本营并评论分享你对本文的学习心得或深度学习的见解,我们将从中选出5条优质评论,各送出《深入浅出神经网络与深度学习》一本。活动截止时间为8月29日晚8点。
假设你是工程师,接到一项任务:从头开始设计计算机。某天,你正在工作室设计逻辑电路,例如构建与门、或门等。这时,老板带着坏消息进来了:客户刚刚提了一个奇怪的设计需求——整个计算机的电路深度限于两层,如图5-1所示。
你惊呆了,跟老板说道:“他们疯了吧!”
老板说:“我也觉得他们疯了,但是客户至上,只能设法满足他们。”
实际上,客户提出的需求并不过分。假设你能使用某种特殊的逻辑对任意多的输入执行AND运算,还能使用多输入的与非门对多个输入执行AND运算和NOT运算。由这类特殊的逻辑门构建出来的双层深度电路就可以计算任何函数。
理论上成立并不代表这是一个好的想法。在实际解决电路设计问题或其他大多数算法问题时,通常要考虑如何解决子问题,然后逐步集成这些子问题的解。换言之,要通过多层抽象来得到最终解。
假设设计一个逻辑电路来对两个数做乘法,我们希望基于计算两个数之和的子电路来构建该逻辑电路。计算两个数之和的子电路是构建在用于两位相加的子子电路上的。电路大致如图5-2所示。
最终的电路至少包含3层。实际上,这个电路很可能超过3层,因为可以将子任务分解成更小的单元,但基本思想就是这样。
可见深度电路让设计过程变得更简单,但对于设计本身帮助并不大。其实用数学可以证明,对于某些函数计算,浅层电路所需的电路单元要比深度电路多得多。例如20世纪80年代初的一些著名的论文已经提出,通过浅层电路计算比特集合的奇偶性需要指数级的逻辑门。然而,如果使用更深的电路,那么可以使用规模很小的电路来计算奇偶性:仅仅需要计算比特对的奇偶性,然后使用这些结果来计算比特对的对的奇偶性,以此类推,从而得出整体的奇偶性。这样一来,深度电路就能在本质上超过浅层电路了。
前文一直将神经网络看作疯狂的客户,几乎讲到的所有神经网络都只包含一层隐藏神经元(另外还有输入层和输出层),如图5-3所示。
这些简单的神经网络已经非常有用了,前面使用这样的神经网络识别手写数字,准确率高达98%!而且,凭直觉来看,拥有更多隐藏层的神经网络会更强大,如图5-4所示。
这样的神经网络可以使用中间层构建出多层抽象,正如在布尔电路中所做的那样。如果进行视觉模式识别,那么第1层的神经元可能学会识别边;第2层的神经元可以在此基础上学会识别更加复杂的形状,例如三角形或矩形;第3层将能够识别更加复杂的形状,以此类推。有了这些多层抽象,深度神经网络似乎可以学习解决复杂的模式识别问题。正如电路示例所体现的那样,理论研究表明深度神经网络本质上比浅层神经网络更强大。
如何训练深度神经网络呢?本章尝试使用我们熟悉的学习算法——基于反向传播的随机梯度下降,来训练深度神经网络。但是,这会产生问题,因为我们的深度神经网络并不比浅层神经网络的性能强多少。
这似乎与前面的讨论相悖,就此退缩吗?当然不,下面深入探究使得深度神经网络训练困难的原因。仔细研究便会发现,在深度神经网络中,不同层的学习速度差异很大。后面的层正常学习时,前面的层常常会在训练中停滞不前,基本上学不到什么。这种停滞并不是因为运气不佳,而是有着更根本的原因,并且这些原因和基于梯度的学习技术相关。
随着更加深入地理解这个问题,也会发现相反的情形:前面的层可能学习得很好,但是后面的层停滞不前。实际上,我们发现在深度神经网络中使用基于梯度下降的学习算法本身存在不稳定性。这种不稳定性使得前面或后面的层的学习过程阻滞。
这的确是个坏消息,但真正理解了这些难点后,就能掌握高效训练深度神经网络的关键所在。而且这些发现也是第6章的预备知识,届时会介绍如何使用深度学习解决图像识别问题。
梯度消失问题
在训练深度神经网络时,究竟哪里出了问题?
为了回答这个问题,首先回顾一下使用单一隐藏层的神经网络示例。这里仍以MNIST数字分类问题作为研究和试验的对象。
你也可以使用自己的计算机训练神经网络。如果想同步跟随这些步骤,需要用到NumPy,可以使用如下命令复制所有代码:
git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git
如果你不使用Git,可以直接在随书下载的压缩包里找到数据和代码。
进入src子目录,在Python shell中加载MNIST数据:
>>> import mnist_loader
>>> training_data, validation_data, test_data = \
... mnist_loader.load_data_wrapper()
初始化神经网络:
>>> import network2
>>> net = network2.Network([784, 30, 10])
该神经网络有784个输入神经元,对应输入图片的28×28 = 784个像素点。我们设置隐藏神经元为30个,输出神经元为10个,对应10个MNIST数字(0~9)。
训练30轮,小批量样本大小为10,学习率
>>> net.SGD(training_data, 30, 10, 0.1, lmbda=5.0,
... evaluation_data=validation_data, monitor_evaluation_accuracy=True)
最终的分类准确率为96.48%(也可能不同,每次运行实际上都会有一点点偏差),这和前面的结果相似。
接下来增加另外一个隐藏层,它也包含30个神经元,并使用相同的超参数进行训练:
>>> net = network2.Network([784, 30, 30, 10])
>>> net.SGD(training_data, 30, 10, 0.1, lmbda=5.0,
... evaluation_data=validation_data, monitor_evaluation_accuracy=True)
分类准确率稍有提升,到了96.90%,这说明增加深度有效果,那就再增加一个隐藏层,它同样有30个神经元:
>>> net = network2.Network([784, 30, 30, 30, 10])
>>> net.SGD(training_data, 30, 10, 0.1, lmbda=5.0,
... evaluation_data=validation_data, monitor_evaluation_accuracy=True)
结果分类准确率不仅没有提升,反而下降到了96.57%,这与最初的浅层神经网络相差无几。尝试再增加一层:
>>> net = network2.Network([784, 30, 30, 30, 30, 10])
>>> net.SGD(training_data, 30, 10, 0.1, lmbda=5.0,
... evaluation_data=validation_data, monitor_evaluation_accuracy=True)
分类准确率继续下降,变为96.53%。虽然这从统计角度看算不上显著下降,但释放出了不好的信号。
这种现象非常奇怪。根据常理判断,额外的隐藏层能让神经网络学到更加复杂的分类函数,然后在分类时表现得更好。按理说不应该变差,有了额外的神经元层,再糟糕也不过是没有作用,然而情况并非如此。
这究竟是为什么呢?理论上,额外的隐藏层的确能够起作用,然而学习算法没有找到正确的权重和偏置。下面研究学习算法本身出了什么问题,以及如何改进。
为了直观理解这个问题,可以将神经网络的学习过程可视化。图5-5展示了[784,30,30,10]神经网络的一部分——两个隐藏层,每层各有30个神经元。图中每个神经元都有一个条形统计图,表示在神经网络学习时该神经元改变的速度,长条代表权重和偏置变化迅速,反之则代表变化缓慢。确切地说,这些条代表每个神经元上的
简单起见,图5-5只展示了每个隐藏层最上方的6个神经元。之所以没有展示输入神经元,是因为它们没有需要学习的权重或偏置;之所以没有展示输出神经元,是因为这里进行的是层与层的比较,而比较神经元数量相同的两层更为合理。神经网络初始化后立即得到了训练前期的结果,如图5-5所示。
该神经网络是随机初始化的,因此神经元的学习速度其实相差较大,而且隐藏层2上的条基本上要比隐藏层1上的条长,所以隐藏层2的神经元学习得更快。这仅仅是一个巧合吗?这能否说明第2个隐藏层的神经元一般会比第1个隐藏层的神经元学习得更快呢?
借助以上定义,在和图5-5相同的配置下,
如果添加更多隐藏层,会如何呢?如果有3个隐藏层,比如一个[784,30,30,30,10]神经网络,那么对应的学习速度分别是0.012、0.060和0.283,其中前面两个隐藏层的学习速度还是慢于最后的隐藏层。假设再增加一个包含30个神经元的隐藏层,那么对应的学习速度分别是0.003、0.017、0.070和0.285。还是相同的模式:前面的隐藏层比后面的隐藏层学习得更慢。
这就是训练开始时的学习速度,即刚刚初始化之后的情况。那么随着训练的推进,学习速度会发生怎样的变化呢?以只有两个隐藏层的神经网络为例,其学习速度的变化如图5-6所示。
这些结果产生自对1000幅训练图像应用梯度下降算法,训练了500轮。这与通常的训练方式不同,没有使用小批量方式,仅仅使用了1000幅训练图像,而不是全部的50 000幅图像。这并不是什么新尝试,也不是敷衍了事,而显示了使用小批量随机梯度下降会让结果包含更多噪声(尽管在平均噪声时结果很相似)。可以使用确定好的参数对结果进行平滑处理,以便看清楚真实情况。
如图5-6所示,两个隐藏层一开始速度便不同,二者的学习速度在触底前迅速下降。此外,第1层的学习速度比第2层慢得多。
更复杂的神经网络情况如何呢?下面进行类似的试验,但这次神经网络有3个隐藏层([784,30,30,30,10]),如图5-7所示。
同样,前面的隐藏层比后面的隐藏层学习得更慢。最后一个试验用到4个隐藏层([784,30,30,30,30,10]),看看情况如何,如图5-8所示。
同样的情况出现了,前面的隐藏层慢于后面的隐藏层。其中隐藏层1的学习速度跟隐藏层4的差了两个数量级,即前者是后者的1/100,难怪之前训练这些神经网络时出现了问题。
这就有了重要发现:至少在某些深度神经网络中,梯度在隐藏层反向传播时倾向于变小。这意味着前面的隐藏层中的神经元比后面的隐藏层中的神经元学习得更慢。本节只研究了一个神经网络,其实多数神经网络存在这个现象,即梯度消失问题。
为何会出现梯度消失问题呢?如何避免它呢?在训练深度神经网络时如何处理这个问题呢?实际上,这个问题并非不可避免,然而替代方法并不完美,也会出现问题:前面的层中的梯度会变得非常大!这被称为梯度爆炸问题,它不比梯度消失问题容易处理。一般而言,深度神经网络中的梯度是不稳定的,在前面的层中可能消失,可能“爆炸”。这种不稳定性是基于梯度学习的深度神经网络存在的根本问题,也就是需要理解的地方。如果可能,应该采取恰当的措施解决该问题。
关于梯度消失(或不稳定),一种观点是确定这真的成问题。暂时换一个话题,假设要最小化一元函数
当然,实际情况并非如此。想想随机初始化神经网络中的权重和偏置。对于任意任务,单单使用随机初始化的值难以获得良好结果。具体而言,考虑MNIST问题中神经网络第1层的权重,随机初始化意味着第1层丢失了输入图像的几乎所有信息。即使后面的层能得到充分的训练,这些层也会因为没有充足的信息而难以识别输入图像。因此,第1层不进行学习是行不通的。如果继续训练深度神经网络,就需要弄清楚如何解决梯度消失问题。
梯度消失的原因
为了弄清楚梯度消失问题出现的原因,看一个极简单的深度神经网络:每层都只有单一神经元。图5-9展示了有3个隐藏层的神经网络。
表达式结构如下:每个神经元都有
你可以不深究这个表达式,直接跳到下文讨论为何出现梯度消失的内容。这样做不会影响理解,因为实际上该表达式只是反向传播的特例。不过,对于该表达式为何正确,了解一下也很有趣(可能还会给你有益的启示)。
假设对偏置
5.2.1 为何出现梯度消失
梯度的完整表达式如下:
除了最后一项,该表达式几乎就是一系列
该导数在
更具体一点,比较
这两个表达式有很多项相同,但
当然,以上并非梯度消失问题的严谨证明,而是一个不太正式的论断,可能还有别的一些原因。我们尤其想知道权重
是否不再满足
5.2.2 梯度爆炸问题
下面分析梯度爆炸的原因。举的例子可能不那么自然:固定神经网络中的参数,以确保发生梯度爆炸。即使不太自然,这个例子也能说明梯度爆炸确实会发生(而非假设)。
5.2.3 梯度不稳定问题
根本问题其实不是梯度消失问题或梯度爆炸问题,而是前面的层上的梯度来自后面的层上项的乘积。当层过多时,神经网络就会变得不稳定。让所有层的学习速度都近乎相同的唯一方式是所有这些项的乘积达到一种平衡。如果没有某种机制或者更加本质的保证来达到平衡,那么神经网络就很容易不稳定。简而言之,根本问题是神经网络受限于梯度不稳定问题。因此,如果使用基于梯度的标准学习算法,那么不同的层会以不同的速度学习。
练 习
在关于梯度消失问题的讨论中,我们采用了
5.2.4 梯度消失问题普遍存在
如前所述,在神经网络中,前面的层可能会出现梯度消失或梯度爆炸。实际上,在使用sigmoid神经元时,通常发生的是梯度消失,原因见表达式
问 题
考虑乘积
(1) 证明这种情况只在
(2) 假设
赠书活动
想要了解关于深度学习的更多干货知识,关注AI科技大本营并在评论区分享你对本文的学习心得或深度学习的见解,我们将从中选出5条优质评论,各送出《深入浅出神经网络与深度学习》一本。
▼ 扫码或点击阅读原文获取本书详情 ▼